# error codes:
# 10 - connection unexpectedly closed

# 31 - abbort command. no connection established 
# 32 - invalid server response
package Net::Coro::EV::FTP::Command;
use common::sense;
our $VERSION = 0.01;

use EV;
our $TIMEOUT = 30; # seconds


sub new {
	my ($class, $socket) = @_;

	my $this = {};
	bless $this, $class;
	
	$this->{_SOCKET} = $socket;
	$this->{_TIMEOUT} = $TIMEOUT;
	
	return $this;
}


sub connect {
	die(__PACKAGE__." connect: arguments count less then 2") if $#_ < 2;
	my ($this, $host, $cb, @prm) = @_;
	$this->{_CB} = $cb;
	$this->{_PRM} = \@prm;
	$this->{_SOCKET}->connect($host, 21, $this);
}

sub onconnect {
	my ($this, $wio, $wtm, $serv_ip, $serv_port) = @_;
	return $this->err($this->{_SOCKET}->errm, $this->{_SOCKET}->errc) if !$wio;
	
	$this->{_WIO} = $wio;
	$this->{_WTM} = $wtm;
	
	$this->{_WIO}->data($this);
	$this->{_WIO}->events (EV::READ);
}


sub cmd {
	die(__PACKAGE__." cmd: arguments count less then 2. check command and callback.") if $#_ < 2;
	my ($this, $cmd, $cb, @prm) = @_;
	
	$this->{_CB} = $cb;
	$this->{_PRM} = \@prm;
	return $this->err("no connection established", 11)  if !$this->{_WIO};
	
	$this->{_REQUEST} = $cmd."\r\n";
	$this->{_WIO}->events (EV::WRITE);
	$this->{_WIO}->start();
	$this->{_WTM}->start();
	
	$this->{_WTM}->set($this->{_TIMEOUT}, 0);
}

sub read {
	die(__PACKAGE__." read: arguments count less then 1") if $#_ < 1;
	my ($this, $cb, @prm) = @_;
	
	$this->{_CB} = $cb;
	$this->{_PRM} = \@prm;
	return $this->err("no connection established",11)  if !$this->{_WIO};
	
	$this->{_WIO}->events (EV::READ);
	$this->{_WIO}->start();
	$this->{_WTM}->start();
	
	$this->{_WTM}->set($this->{_TIMEOUT}, 0);	
}

sub onwrite {
	my ($this) = @_;
	return $this->{_REQUEST};
}

sub onread {
	my ($this, $response) = @_;
	$this->{_WTM}->stop();
	$response = delete($this->{_PREVRESP}).$response if $this->{_PREVRESP};
	
	my $is_callback=0;
	# process state machine, but it reads a line instead of a symbol
	my @lines = split (/^/, $response);
	foreach (@lines) {
		# check if callback already triggered, but we continue process lines
		# it means we receive additional data - uncpecified for protocol. it is error
		if ($is_callback) {
			$this->err("protocol get: \n$response\n", 12);
			return;
		}
		
		#check if a line is full and correct
		if (substr($_, -1, 1) ne "\n") {
			$this->{_PREVRESP} = $_;
			return;
		}
			
		# inintial state
		if (!$this->{_CODE}) {
			$this->{_CODE} = substr($_, 0, 3,'');
			my $delim = substr($_, 0, 1,'');
			
			# is this the end ?
			if ($delim eq ' ') {
				s/\s*$//s;
				$this->{_MSG} = $_;
				$is_callback=1;
			}
			
			# is this multiline response ?
			elsif ($delim eq '-') {
				$this->{_MSGDATA} = $_;
			}
			
			# error
			else {
				$this->err("protocol get: ".$this->{_CODE}.$delim.$_, 12);
				return;
			}
		}
		
		# continue read multiline response
		else {
			my $code = substr($_, 0, 3,'');
			my $delim = substr($_, 0, 1,'');
			
			# is this the end ?
			if ($code eq $this->{_CODE} and $delim eq ' ') {
				s/\s*$//s;
				$this->{_MSG} = $_;
				$is_callback=1;
			}
			# or continue
			else {
				$this->{_MSGDATA} .= $code. $delim. $_;
			}
		}
	}
	
	$this->callback if ($is_callback);
}



######## COMMON ARCHITECTURAL METHODS AND EVENT HANDLERS ###################
sub onwritten {
	my ($this, $w) = @_;
	$w->events (EV::READ);
}

sub onclose {
	my ($this) = @_;
	# make desigion what to do
	# default is to send error
	$this->err("connectnion closed by ftp host",10);
}

sub onbreak {
	my ($this, $msg, $code) = @_;
	# make desigion what to do
	# default is to send error
	$this->err($msg, 2);
}

sub callback {
	my ($this) = @_;
	$this->{_WIO}->stop();
	$this->{_WTM}->stop();
	
	undef $this->{_ERRM};
	undef $this->{_ERRC};
	
	delete $this->{_ERR};
	my @prm = @{delete $this->{_PRM}};
	(delete $this->{_CB})->( 
		$this,
		delete($this->{_CODE}), 
		delete($this->{_MSG}), 
		delete($this->{_MSGDATA}),
		@prm		
	);
}


sub err {
	my ($this, $msg, $code) = @_;
	$this->{_ERRM} = $msg;
	$this->{_ERRC} = $code;

	# close all
	$this->close;
	
	# send error
	my @prm = @{delete $this->{_PRM}};
	my $cb = delete $this->{_CB};
	my $errcb = delete $this->{_ERR};
	if (!$errcb) {
		$cb->($this, undef,$msg,$code,$cb, @prm);
	} 
	else {
		$errcb->($this, $msg,$code,$cb,$errcb, @prm);
	}
}


sub close {
	my ($this) =@_;
	
	my $tm = delete $this->{_WTM};
	$tm->stop() if defined $tm;
	
	my $w = delete $this->{_WIO};
	if (defined $w) {
		$w->stop();
		my $fh = $w->fh;
		shutdown($fh, 2);
		CORE::close($fh);
	}
}

sub stop {
	my ($this) = @_;
	$this->{_SOCKET}->stop();
	$this->close();
	delete $this->{_CB};
	delete $this->{_PRM};
	delete $this->{_ERR};
}

sub error {
	my ($this, $cberr) = @_;
	$this->{_ERR} = $cberr;
}


############ COMMON PROPIRTIES ##############
sub errc {$_[0]->{_ERRC}}
sub errm {$_[0]->{_ERRM}}

sub timeout {
	my ($this, $val) = @_;	
	if (defined $val) {
		$this->{_TIMEOUT} = $val;
		$this->{_SOCKET}->timeout($val);
	}
	return $this->{_TIMEOUT};
}
sub bindip {$_[0]->{_SOCKET}->bindip($_[1])}

sub id {	
	my ($this, $id) = @_;
	if (defined $id) {
		$this->{_ID} = $id;
		$this->{_SOCKET}->id($id);
	}
	$this->{_ID};
}
1;